12

一般的后台管理系统功能都比较繁多,存在有多级菜单的需求,但是在这种项目里往往keep-alive的表现却非常不稳定,有时候某个页面可以缓存,但是点几下就发现缓存丢了;有时候不知道怎么回事又死活不缓存了。

造成这个问题的原因是: 多级路由组件嵌套。
具体分析: 假如一个后台管理系统,有一个main.vue是所有页面的框架, 里面有这样一段代码

          <keep-alive>
            <router-view v-if="$route.meta.keepAlive"></router-view>
          </keep-alive>
          <router-view v-if="!$route.meta.keepAlive"></router-view>

然后建立了一个父级菜单,页面是ChildView.vue, 里面的代码如下

<router-view></router-view>

假设这个父菜单里有两个子菜单,分别为 A和B, 然后A设置为缓存(keepAlive=true), B不缓存(keepAlive=false);
当点击A菜单的时候,系统缓存了ChildView.vue组件。
当点击B菜单的时候,由于B设置的不缓存,所以导致ChildView.vue组件被销毁
这就是keep-alive为什么会失效的根本原因。

还有更复杂的,比如同一个菜单里的子菜单可以缓存,但是点击另外一个父菜单或者父菜单下的子菜单却不缓存, 究其原理和上面的分析是一样的原因,就是多级菜单,多个共用组件导致的keepAlive缓存失效, keepAlive根本没考虑到页面缓存的复杂性。

以下几种表现也是这个问题造成的原因之一:

  1. activated和deactivated不触发
  2. 从A页面进入B页面发现的时候发现A页面的接口又会被重复触发调用

分析问题:
既然是多个router-view嵌套并且共用的情况下造成的,那么如果只存在一个router-view,也就是只需要main.vue作为框架内所有页面的容器,就不会有这个问题。

实际上是不是多级菜单对于项目或者业务上来讲一点都不影响,只是界面显示上需要,让用户能更快点击到自己需要的功能页面而已。 既然这样的话,显示的菜单保留多级的,实际的router弄成一级, 将显示菜单和业务router分离开。

解决问题:
首先将配置好的多级router用vuex缓存起来,用作展示的菜单。
然后将router转换一下,转换成一级菜单,用addRoutes异步添加到router里面。

局部示例代码:

const formatRouter = (routes, newRoutes = []) => {
  routes.map(item => {
    if (item.children && item.children.length > 0) formatRouter(item.children, newRoutes);
    newRoutes.push(item);
  })
  return newRoutes;
}
let flatRoutes = formatRouter(routes);
router.addRoutes(flatRoutes);

然后面包屑导航要调整一下,大部分逻辑都是从route.matched里面获取的,但是现在router全是一级的,我们要从展示的菜单数据里面拿面包屑导航数据。

示例代码:

/**
 * 自定义查找字段, 根据最后一级某个字段查找完整树(整个父类)
 * @param {*} val     要查找对比的值
 * @param {*} data    要查找的数据
 * @param {*} fKey    要查找对比的字段
 */ 
const recursiveTreeByLastLevel = (val, data, fKey = 'value') => {
  let rData = [];
  for (let i = 0, len = data.length; i < len; i++) {
    rData.push(data[i]);
    if (data[i].children && data[i].children.length > 0) {
      rData = rData.concat(recursiveTreeByLastLevel(val, data[i].children, fKey));
      if (rData.some(item => item[fKey] === val)) return rData;
    }
    if (data[i][fKey] === val) return rData;
    rData = [];
  }
  return rData;
}
 
router.afterEach((to, from, next) => {
  var routerList = recursiveTreeByLastLevel(to.name, store.state.sidebarMenu, 'name')
  store.commit('setCrumbList', routerList) // 通过vuex缓存
})

搞完,收工!


Mrsum
959 声望21 粉丝